#include "src/ltopt.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "src/ltbase.h"
#include "src/lterr.h"

lt_UsingType(lt, USize);
lt_UsingType(lt, U8);

typedef struct {
    bool mask[256];
} CharMask;

static bool
GetMask(CharMask* mask, char c) {
    return mask->mask[(U8)c];
}

static bool
SetMask(CharMask* mask, char c, bool b) {
    return mask->mask[(U8)c] = b;
}

lterr_Code
ltopt_Parse(int argc, char const* argv[], char const* take_opts,
            ltopt_OnArgument on_argument, void* ctx) {
    CharMask take_opt_mask = {0};
    for (; *take_opts != 0; ++take_opts)
        SetMask(&take_opt_mask, *take_opts, true);

    bool end_of_opt = false;

    for (int argi = 1; argi < argc; ++argi) {
        char const* arg = argv[argi];
        lterr_Code r;

        if (end_of_opt) {
            r = on_argument(ctx, 0, arg);
        } else if (arg[0] != '-' || arg[1] == 0) { /* - */
            r = on_argument(ctx, 0, arg);
        } else if (arg[1] == '-') { /* -- */
            end_of_opt = true;
            r = lterr_kOk;
        } else if (!GetMask(&take_opt_mask, arg[1])) { /* -abc */
            for (USize i = 1; arg[i] != 0; ++i) {
                if (GetMask(&take_opt_mask, arg[i]))
                    return lterr_kError;
                r = on_argument(ctx, arg[i], "true");
                if (r != 0)
                    break;
            }
        } else if (arg[2] == 0) { /* -a [bc] */
            if (argi + 1 >= argc)
                return lterr_kError;
            argi += 1;
            r = on_argument(ctx, arg[1], argv[argi]);
        } else { /* -a[bc] */
            r = on_argument(ctx, arg[1], arg + 2);
        }

        if (r != 0)
            return r;
    }

    return 0;
}

void
ltopt_WriteUsage(ltopt_Usage* usage, FILE* fp) {
    fprintf(fp, "Usage: %s [OPTIONS]", usage->name);

    if (usage->arguments != NULL) {
        for (lt_USize i = 0;; ++i) {
            ltopt_ArgumentHelp const* arghelp = &usage->arguments[i];
            if (arghelp->name == NULL)
                break;

            if (arghelp->multiple) {
                fprintf(fp, " [%s]...", arghelp->name);
            } else {
                fprintf(fp, " [%s]", arghelp->name);
            }
        }
    }

    fprintf(fp, "\n");

    if (usage->help != NULL) {
        fprintf(fp, "  %s\n", usage->help);
        fprintf(fp, "\n");
    }

    if (usage->options != NULL) {
        fprintf(fp, "Options:\n");

        for (USize i = 0;; ++i) {
            ltopt_OptionHelp const* opthelp = &usage->options[i];
            if (opthelp->name == 0)
                break;

            fprintf(fp, "  -%c\t%s\n", opthelp->name, opthelp->help);
        }

        fprintf(fp, "\n");
    }

    if (usage->arguments != NULL) {
        fprintf(fp, "Arguments:\n");

        for (USize i = 0;; ++i) {
            ltopt_ArgumentHelp const* arghelp = &usage->arguments[i];
            if (arghelp->name == NULL)
                break;

            fprintf(fp, "  %s\t%s\n", arghelp->name, arghelp->help);
        }
    }
}
